// (C) Szymon Piechowicz, February 2012

#include "adc_manager.h"
#include <avr/interrupt.h>
#include "common.h"


static void adc_start(void);
static void adc_stop(void);


static int8_t num_active_bits = 0;
static int8_t current_bit;
static int8_t next_bit[8]; // bit -> next bit
static adc_manager_callback callbacks[8]; // bit -> callback
static uint8_t min_changes_to_report[8]; // bit -> min change
static bool did_report_first_result[8]; // bit -> did report first result?
static uint8_t last_reported_results[8]; // bit -> result


void adc_manager_init(void)
{
  ADMUX |= (1 << REFS0); // set reference voltage to AVCC
  ADMUX |= (1 << ADLAR); // left adjust results
  ADCSRA |= (1 << ADATE); // enable auto-trigger
  ADCSRA |= (1 << ADIE); // enable ADC interrupts
  ADCSRA |= (1 << ADPS2) | (1 << ADPS1) | (1 << ADPS0); // set prescaler / 128
}

void adc_manager_start_measuring(int8_t bit, adc_manager_callback callback,
    uint8_t min_change_to_report)
{
  callbacks[bit] = callback;
  min_changes_to_report[bit] = min_change_to_report;
  did_report_first_result[bit] = false;
  if (++num_active_bits == 1)
  {
    next_bit[bit] = bit;
    current_bit = bit;
    adc_start();
  }
  else
  {
    next_bit[bit] = next_bit[current_bit];
    next_bit[current_bit] = bit;
  }
}

void adc_manager_stop_measuring(int8_t bit)
{
  if (--num_active_bits == 0)
    adc_stop();
  else
  {
    for (int i = 0; i < 8; ++i)
      if (next_bit[i] == bit)
      {
        next_bit[i] = next_bit[bit];
        break;
      }
  }
}

static void adc_start(void)
{
  ADMUX = (ADMUX & 0xE0) | current_bit;
  ADCSRA |= (1 << ADEN); // enable ADC
  ADCSRA |= (1 << ADSC); // start ADC
}

static void adc_stop(void)
{
  ADCSRA &= ~(1 << ADEN); // disable ADC
}

ISR(ADC_vect)
{
  int8_t measured_bit = current_bit;
  uint8_t result = ADCH;
  current_bit = ADMUX & 0x1F; // next conversion already started
  ADMUX = (ADMUX & 0xE0) | next_bit[current_bit];
  // Callback.
  if (did_report_first_result[measured_bit])
  {
    if (result < last_reported_results[measured_bit])
    {
      if (last_reported_results[measured_bit] - result <
          min_changes_to_report[measured_bit])
        return;
    }
    else
    {
      if (result - last_reported_results[measured_bit] <
          min_changes_to_report[measured_bit])
        return;
    }
  }
  else
    did_report_first_result[measured_bit] = true;
  last_reported_results[measured_bit] = result;
  callbacks[measured_bit](measured_bit, result);
}